/** @file
  The internal functions and routines to transmit the IP6 packet.

  Copyright (c) 2009 - 2015, Intel Corporation. All rights reserved.<BR>

  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php.

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "Ip6Impl.h"

UINT32 mIp6Id;

/**
  Output all the available source addresses to a list entry head SourceList. The
  number of source addresses are also returned.

  @param[in]       IpSb             Points to an IP6 service binding instance.
  @param[out]      SourceList       The list entry head of all source addresses.
                                    It is the caller's responsiblity to free the
                                    resources.
  @param[out]      SourceCount      The number of source addresses.

  @retval EFI_SUCCESS           The source addresses were copied to a list entry head
                                SourceList.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate resources to complete the operation.

**/
EFI_STATUS
Ip6CandidateSource (
  IN IP6_SERVICE            *IpSb,
  OUT LIST_ENTRY            *SourceList,
  OUT UINT32                *SourceCount
  )
{
  IP6_INTERFACE             *IpIf;
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *Entry2;
  IP6_ADDRESS_INFO          *AddrInfo;
  IP6_ADDRESS_INFO          *Copy;

  *SourceCount = 0;

  if (IpSb->LinkLocalOk) {
    Copy = AllocatePool (sizeof (IP6_ADDRESS_INFO));
    if (Copy == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    Copy->Signature         = IP6_ADDR_INFO_SIGNATURE;
    IP6_COPY_ADDRESS (&Copy->Address, &IpSb->LinkLocalAddr);
    Copy->IsAnycast         = FALSE;
    Copy->PrefixLength      = IP6_LINK_LOCAL_PREFIX_LENGTH;
    Copy->ValidLifetime     = (UINT32) IP6_INFINIT_LIFETIME;
    Copy->PreferredLifetime = (UINT32) IP6_INFINIT_LIFETIME;

    InsertTailList (SourceList, &Copy->Link);
    (*SourceCount)++;
  }

  NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) {
    IpIf = NET_LIST_USER_STRUCT (Entry, IP6_INTERFACE, Link);

    NET_LIST_FOR_EACH (Entry2, &IpIf->AddressList) {
      AddrInfo = NET_LIST_USER_STRUCT_S (Entry2, IP6_ADDRESS_INFO, Link, IP6_ADDR_INFO_SIGNATURE);

      if (AddrInfo->IsAnycast) {
        //
        // Never use an anycast address.
        //
        continue;
      }

      Copy = AllocateCopyPool (sizeof (IP6_ADDRESS_INFO), AddrInfo);
      if (Copy == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }

      InsertTailList (SourceList, &Copy->Link);
      (*SourceCount)++;
    }
  }

  return EFI_SUCCESS;
}

/**
  Calculate how many bits are the same between two IPv6 addresses.

  @param[in]       AddressA         Points to an IPv6 address.
  @param[in]       AddressB         Points to another IPv6 address.

  @return The common bits of the AddressA and AddressB.

**/
UINT8
Ip6CommonPrefixLen (
  IN EFI_IPv6_ADDRESS       *AddressA,
  IN EFI_IPv6_ADDRESS       *AddressB
  )
{
  UINT8                     Count;
  UINT8                     Index;
  UINT8                     ByteA;
  UINT8                     ByteB;
  UINT8                     NumBits;

  Count = 0;
  Index = 0;

  while (Index < 16) {
    ByteA = AddressA->Addr[Index];
    ByteB = AddressB->Addr[Index];

    if (ByteA == ByteB) {
      Count += 8;
      Index++;
      continue;
    }

    //
    // Check how many bits are common between the two bytes.
    //
    NumBits = 8;
    ByteA   = (UINT8) (ByteA ^ ByteB);

    while (ByteA != 0) {
      NumBits--;
      ByteA = (UINT8) (ByteA >> 1);
    }

    return (UINT8) (Count + NumBits);
  }

  return Count;
}

/**
  Output all the available source addresses to a list entry head SourceList. The
  number of source addresses are also returned.

  @param[in]       IpSb             Points to a IP6 service binding instance.
  @param[in]       Destination      The IPv6 destination address.
  @param[out]      Source           The selected IPv6 source address according to
                                    the Destination.

  @retval EFI_SUCCESS           The source addresses were copied to a list entry
                                head SourceList.
  @retval EFI_NO_MAPPING        The IPv6 stack is not auto configured.

**/
EFI_STATUS
Ip6SelectSourceAddress (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *Destination,
  OUT EFI_IPv6_ADDRESS      *Source
  )
{
  EFI_STATUS                Status;
  LIST_ENTRY                SourceList;
  UINT32                    SourceCount;
  UINT8                     ScopeD;
  LIST_ENTRY                *Entry;
  IP6_ADDRESS_INFO          *AddrInfo;
  IP6_PREFIX_LIST_ENTRY     *Prefix;
  UINT8                     LastCommonLength;
  UINT8                     CurrentCommonLength;
  EFI_IPv6_ADDRESS          *TmpAddress;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);

  Status = EFI_SUCCESS;
  InitializeListHead (&SourceList);

  if (!IpSb->LinkLocalOk) {
    return EFI_NO_MAPPING;
  }

  //
  // Rule 1: Prefer same address.
  //
  if (Ip6IsOneOfSetAddress (IpSb, Destination, NULL, NULL)) {
    IP6_COPY_ADDRESS (Source, Destination);
    goto Exit;
  }

  //
  // Rule 2: Prefer appropriate scope.
  //
  if (IP6_IS_MULTICAST (Destination)) {
    ScopeD = (UINT8) (Destination->Addr[1] >> 4);
  } else if (NetIp6IsLinkLocalAddr (Destination)) {
    ScopeD = 0x2;
  } else {
    ScopeD = 0xE;
  }

  if (ScopeD <= 0x2) {
    //
    // Return the link-local address if it exists
    // One IP6_SERVICE only has one link-local address.
    //
    IP6_COPY_ADDRESS (Source, &IpSb->LinkLocalAddr);
    goto Exit;
  }

  //
  // All candidate source addresses are global unicast address.
  //
  Ip6CandidateSource (IpSb, &SourceList, &SourceCount);

  if (SourceCount == 0) {
    Status = EFI_NO_MAPPING;
    goto Exit;
  }

  IP6_COPY_ADDRESS (Source, &IpSb->LinkLocalAddr);

  if (SourceCount == 1) {
    goto Exit;
  }

  //
  // Rule 3: Avoid deprecated addresses.
  // TODO: check the "deprecated" state of the stateful configured address
  //
  NET_LIST_FOR_EACH (Entry, &IpSb->AutonomousPrefix) {
    Prefix = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
    if (Prefix->PreferredLifetime == 0) {
      Ip6RemoveAddr (NULL, &SourceList, &SourceCount, &Prefix->Prefix, Prefix->PrefixLength);

      if (SourceCount == 1) {
        goto Exit;
      }
    }
  }

  //
  // TODO: Rule 4: Prefer home addresses.
  // TODO: Rule 5: Prefer outgoing interface.
  // TODO: Rule 6: Prefer matching label.
  // TODO: Rule 7: Prefer public addresses.
  //

  //
  // Rule 8: Use longest matching prefix.
  //
  LastCommonLength = Ip6CommonPrefixLen (Source, Destination);
  TmpAddress       = NULL;

  for (Entry = SourceList.ForwardLink; Entry != &SourceList; Entry = Entry->ForwardLink) {
    AddrInfo = NET_LIST_USER_STRUCT_S (Entry, IP6_ADDRESS_INFO, Link, IP6_ADDR_INFO_SIGNATURE);

    CurrentCommonLength = Ip6CommonPrefixLen (&AddrInfo->Address, Destination);
    if (CurrentCommonLength > LastCommonLength) {
      LastCommonLength = CurrentCommonLength;
      TmpAddress       = &AddrInfo->Address;
    }
  }

  if (TmpAddress != NULL) {
    IP6_COPY_ADDRESS (Source, TmpAddress);
  }

Exit:

  Ip6RemoveAddr (NULL, &SourceList, &SourceCount, NULL, 0);

  return Status;
}

/**
  Select an interface to send the packet generated in the IP6 driver
  itself: that is, not by the requests of the IP6 child's consumer. Such
  packets include the ICMPv6 echo replies and other ICMPv6 error packets.

  @param[in]  IpSb                 The IP4 service that wants to send the packets.
  @param[in]  Destination          The destination of the packet.
  @param[in, out]  Source          The source of the packet.

  @return NULL if no proper interface is found, otherwise, the interface that
          can be used to send the system packet from.

**/
IP6_INTERFACE *
Ip6SelectInterface (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *Destination,
  IN OUT EFI_IPv6_ADDRESS   *Source
  )
{
  EFI_STATUS                Status;
  EFI_IPv6_ADDRESS          SelectedSource;
  IP6_INTERFACE             *IpIf;
  BOOLEAN                   Exist;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
  ASSERT (Destination != NULL && Source != NULL);

  if (NetIp6IsUnspecifiedAddr (Destination)) {
    return NULL;
  }

  if (!NetIp6IsUnspecifiedAddr (Source)) {
    Exist = Ip6IsOneOfSetAddress (IpSb, Source, &IpIf, NULL);
    ASSERT (Exist);

    return IpIf;
  }

  //
  // If source is unspecified, select a source according to the destination.
  //
  Status = Ip6SelectSourceAddress (IpSb, Destination, &SelectedSource);
  if (EFI_ERROR (Status)) {
    return IpSb->DefaultInterface;
  }

  Ip6IsOneOfSetAddress (IpSb, &SelectedSource, &IpIf, NULL);
  IP6_COPY_ADDRESS (Source, &SelectedSource);

  return IpIf;
}

/**
  The default callback function for the system generated packet.
  It will free the packet.

  @param[in]  Packet        The packet that transmitted.
  @param[in]  IoStatus      The result of the transmission, succeeded or failed.
  @param[in]  LinkFlag      Not used when transmitted. Check IP6_FRAME_CALLBACK
                            for reference.
  @param[in]  Context       The context provided by us.

**/
VOID
Ip6SysPacketSent (
  NET_BUF                   *Packet,
  EFI_STATUS                IoStatus,
  UINT32                    LinkFlag,
  VOID                      *Context
  )
{
  NetbufFree (Packet);
  Packet = NULL;
}

/**
  Prefix an IP6 basic head and unfragmentable extension headers and a fragment header
  to the Packet. Used for IP6 fragmentation.

  @param[in]  IpSb             The IP6 service instance to transmit the packet.
  @param[in]  Packet           The packet to prefix the IP6 header to.
  @param[in]  Head             The caller supplied header.
  @param[in]  FragmentOffset   The fragment offset of the data following the header.
  @param[in]  ExtHdrs          The length of the original extension header.
  @param[in]  ExtHdrsLen       The length of the extension headers.
  @param[in]  LastHeader       The pointer of next header of last extension header.
  @param[in]  HeadLen          The length of the unfragmented part of the IP6 header.

  @retval EFI_BAD_BUFFER_SIZE  There is no enought room in the head space of
                               Packet.
  @retval EFI_SUCCESS          The operation performed successfully.

**/
EFI_STATUS
Ip6PrependHead (
  IN IP6_SERVICE            *IpSb,
  IN NET_BUF                *Packet,
  IN EFI_IP6_HEADER         *Head,
  IN UINT16                 FragmentOffset,
  IN UINT8                  *ExtHdrs,
  IN UINT32                 ExtHdrsLen,
  IN UINT8                  LastHeader,
  IN UINT32                 HeadLen
  )
{
  UINT32                    Len;
  UINT32                    UnFragExtHdrsLen;
  EFI_IP6_HEADER            *PacketHead;
  UINT8                     *UpdatedExtHdrs;
  EFI_STATUS                Status;
  UINT8                     NextHeader;

  UpdatedExtHdrs = NULL;

  //
  // HeadLen is the length of the fixed part of the sequences of fragments, i.e.
  // the unfragment part.
  //
  PacketHead = (EFI_IP6_HEADER *) NetbufAllocSpace (Packet, HeadLen, NET_BUF_HEAD);
  if (PacketHead == NULL) {
    return EFI_BAD_BUFFER_SIZE;
  }

  //
  // Set the head up, convert the host byte order to network byte order
  //
  CopyMem (PacketHead, Head, sizeof (EFI_IP6_HEADER));
  PacketHead->PayloadLength = HTONS ((UINT16) (Packet->TotalSize - sizeof (EFI_IP6_HEADER)));
  Packet->Ip.Ip6            = PacketHead;

  Len              = HeadLen - sizeof (EFI_IP6_HEADER);
  UnFragExtHdrsLen = Len - sizeof (IP6_FRAGMENT_HEADER);

  if (UnFragExtHdrsLen == 0) {
    PacketHead->NextHeader = IP6_FRAGMENT;
  }

  //
  // Append the extension headers: firstly copy the unfragmentable headers, then append
  // fragmentation header.
  //
  if ((FragmentOffset & IP6_FRAGMENT_OFFSET_MASK) == 0) {
    NextHeader = Head->NextHeader;
  } else {
    NextHeader = PacketHead->NextHeader;
  }

  Status = Ip6FillFragmentHeader (
             IpSb,
             NextHeader,
             LastHeader,
             ExtHdrs,
             ExtHdrsLen,
             FragmentOffset,
             &UpdatedExtHdrs
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  CopyMem (
    (UINT8 *) (PacketHead + 1),
    UpdatedExtHdrs,
    UnFragExtHdrsLen + sizeof (IP6_FRAGMENT_HEADER)
    );

  FreePool (UpdatedExtHdrs);
  return EFI_SUCCESS;
}

/**
  Transmit an IP6 packet. The packet comes either from the IP6
  child's consumer (IpInstance != NULL) or the IP6 driver itself
  (IpInstance == NULL). It will route the packet, fragment it,
  then transmit all the fragments through an interface.

  @param[in]  IpSb             The IP6 service instance to transmit the packet.
  @param[in]  Interface        The IP6 interface to transmit the packet. Ignored
                               if NULL.
  @param[in]  IpInstance       The IP6 child that issues the transmission.  It is
                               NULL if the packet is from the system.
  @param[in]  Packet           The user data to send, excluding the IP header.
  @param[in]  Head             The caller supplied header. The caller should set
                               the  following header fields: NextHeader, HopLimit,
                               Src, Dest, FlowLabel, PayloadLength. This function
                               will fill in the Ver, TrafficClass.
  @param[in]  ExtHdrs          The extension headers to append to the IPv6 basic
                               header.
  @param[in]  ExtHdrsLen       The length of the extension headers.
  @param[in]  Callback         The callback function to issue when transmission
                               completed.
  @param[in]  Context          The opaque context for the callback.

  @retval EFI_INVALID_PARAMETER Any input parameter or the packet is invalid.
  @retval EFI_NO_MAPPING        There is no interface to the destination.
  @retval EFI_NOT_FOUND         There is no route to the destination.
  @retval EFI_SUCCESS           The packet successfully transmitted.
  @retval EFI_OUT_OF_RESOURCES  Failed to finish the operation due to lack of
                                resources.
  @retval Others                Failed to transmit the packet.

**/
EFI_STATUS
Ip6Output (
  IN IP6_SERVICE            *IpSb,
  IN IP6_INTERFACE          *Interface   OPTIONAL,
  IN IP6_PROTOCOL           *IpInstance  OPTIONAL,
  IN NET_BUF                *Packet,
  IN EFI_IP6_HEADER         *Head,
  IN UINT8                  *ExtHdrs,
  IN UINT32                 ExtHdrsLen,
  IN IP6_FRAME_CALLBACK     Callback,
  IN VOID                   *Context
  )
{
  IP6_INTERFACE             *IpIf;
  EFI_IPv6_ADDRESS          NextHop;
  IP6_NEIGHBOR_ENTRY        *NeighborCache;
  IP6_ROUTE_CACHE_ENTRY     *RouteCache;
  EFI_STATUS                Status;
  UINT32                    Mtu;
  UINT32                    HeadLen;
  UINT16                    FragmentOffset;
  UINT8                     *LastHeader;
  UINT32                    UnFragmentLen;
  UINT32                    UnFragmentHdrsLen;
  UINT32                    FragmentHdrsLen;
  UINT16                    *Checksum;
  UINT16                    PacketChecksum;
  UINT16                    PseudoChecksum;
  UINT32                    Index;
  UINT32                    PacketLen;
  UINT32                    RealExtLen;
  UINT32                    Offset;
  NET_BUF                   *TmpPacket;
  NET_BUF                   *Fragment;
  UINT32                    Num;
  UINT8                     *Buf;
  EFI_IP6_HEADER            *PacketHead;
  IP6_ICMP_HEAD             *IcmpHead;
  IP6_TXTOKEN_WRAP          *Wrap;
  IP6_ROUTE_ENTRY           *RouteEntry;
  UINT8                     *UpdatedExtHdrs;
  UINT8                     NextHeader;
  UINT8                     LastHeaderBackup;
  BOOLEAN                   FragmentHeadInserted;
  UINT8                     *ExtHdrsBackup;
  UINT8                     NextHeaderBackup;
  EFI_IPv6_ADDRESS          Source;
  EFI_IPv6_ADDRESS          Destination;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);

  //
  // RFC2460: Each extension header is an integer multiple of 8 octets long,
  // in order to retain 8-octet alignment for subsequent headers.
  //
  if ((ExtHdrsLen & 0x7) != 0) {
    return EFI_INVALID_PARAMETER;
  }

  LastHeader = NULL;

  Ip6IsExtsValid (
    NULL,
    NULL,
    &Head->NextHeader,
    ExtHdrs,
    ExtHdrsLen,
    FALSE,
    NULL,
    &LastHeader,
    NULL,
    NULL,
    NULL
    );

  //
  // Select an interface/source for system packet, application
  // should select them itself.
  //
  IpIf = Interface;
  if (IpIf == NULL) {
    //
    // IpInstance->Interface is NULL when IpInstance is configured with both stationaddress
    // and destinationaddress is unspecified.
    //
    if (IpInstance == NULL || IpInstance->Interface == NULL) {
      IpIf = Ip6SelectInterface (IpSb, &Head->DestinationAddress, &Head->SourceAddress);
      if (IpInstance != NULL) {
        IpInstance->Interface = IpIf;
      }
    } else {
      IpIf = IpInstance->Interface;
    }
  }

  if (IpIf == NULL) {
    return EFI_NO_MAPPING;
  }

  //
  // Update the common field in Head here.
  //
  Head->Version       = 6;
  Head->TrafficClassL = 0;
  Head->TrafficClassH = 0;

  Checksum            = NULL;
  NextHeader          = *LastHeader;

  switch (NextHeader) {
  case EFI_IP_PROTO_UDP:
    Packet->Udp = (EFI_UDP_HEADER *) NetbufGetByte (Packet, 0, NULL);
    ASSERT (Packet->Udp != NULL);
    if (Packet->Udp->Checksum == 0) {
      Checksum = &Packet->Udp->Checksum;
    }
    break;

  case EFI_IP_PROTO_TCP:
    Packet->Tcp = (TCP_HEAD *) NetbufGetByte (Packet, 0, NULL);
    ASSERT (Packet->Tcp != NULL);
    if (Packet->Tcp->Checksum == 0) {
      Checksum = &Packet->Tcp->Checksum;
    }
    break;

  case IP6_ICMP:
    //
    // Don't send ICMP packet to an IPv6 anycast address.
    //
    if (Ip6IsAnycast (IpSb, &Head->DestinationAddress)) {
      return EFI_INVALID_PARAMETER;
    }

    IcmpHead = (IP6_ICMP_HEAD *) NetbufGetByte (Packet, 0, NULL);
    ASSERT (IcmpHead != NULL);
    if (IcmpHead->Checksum == 0) {
      Checksum = &IcmpHead->Checksum;
    }
    break;

  default:
    break;
  }

  if (Checksum != NULL) {
    //
    // Calculate the checksum for upper layer protocol if it is not calculated due to lack of
    // IPv6 source address.
    //
    PacketChecksum = NetbufChecksum (Packet);
    PseudoChecksum = NetIp6PseudoHeadChecksum (
                      &Head->SourceAddress,
                      &Head->DestinationAddress,
                      NextHeader,
                      Packet->TotalSize
                      );
    *Checksum = (UINT16) ~NetAddChecksum (PacketChecksum, PseudoChecksum);
  }

  Status = Ip6IpSecProcessPacket (
             IpSb,
             &Head,
             LastHeader, // no need get the lasthead value for output
             &Packet,
             &ExtHdrs,
             &ExtHdrsLen,
             EfiIPsecOutBound,
             Context
             );

  if (EFI_ERROR(Status)) {
    return Status;
  }

  LastHeader = NULL;
  //
  // Check incoming parameters.
  //
  if (!Ip6IsExtsValid (
         IpSb,
         Packet,
         &Head->NextHeader,
         ExtHdrs,
         ExtHdrsLen,
         FALSE,
         NULL,
         &LastHeader,
         &RealExtLen,
         &UnFragmentHdrsLen,
         NULL
         )) {
    return EFI_INVALID_PARAMETER;
  }

  if ((RealExtLen & 0x7) != 0) {
    return EFI_INVALID_PARAMETER;
  }

  LastHeaderBackup = *LastHeader;

  //
  // Perform next hop determination:
  // For multicast packets, the next-hop is always the destination address and
  // is considered to be on-link.
  //
  if (IP6_IS_MULTICAST (&Head->DestinationAddress)) {
    IP6_COPY_ADDRESS (&NextHop, &Head->DestinationAddress);
  } else {
    //
    // For unicast packets, use a combination of the Destination Cache, the Prefix List
    // and the Default Router List to determine the IP address of the appropriate next hop.
    //

    NeighborCache = Ip6FindNeighborEntry (IpSb, &Head->DestinationAddress);
    if (NeighborCache != NULL) {
      //
      // Hit Neighbor Cache.
      //
      IP6_COPY_ADDRESS (&NextHop, &Head->DestinationAddress);
    } else {
      //
      // Not in Neighbor Cache, check Router cache
      //
      RouteCache = Ip6Route (IpSb, &Head->DestinationAddress, &Head->SourceAddress);
      if (RouteCache == NULL) {
        return EFI_NOT_FOUND;
      }

      IP6_COPY_ADDRESS (&NextHop, &RouteCache->NextHop);
      Ip6FreeRouteCacheEntry (RouteCache);
    }
  }

  //
  // Examines the Neighbor Cache for link-layer information about that neighbor.
  // DO NOT create neighbor cache if neighbor is itself - when reporting ICMP error.
  //
  if (!IP6_IS_MULTICAST (&NextHop) && !EFI_IP6_EQUAL (&Head->DestinationAddress, &Head->SourceAddress)) {
    NeighborCache = Ip6FindNeighborEntry (IpSb, &NextHop);
    if (NeighborCache == NULL) {
      NeighborCache = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, &NextHop, NULL);

      if (NeighborCache == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }

      //
      // Send out multicast neighbor solicitation for address resolution immediatly.
      //
      Ip6CreateSNMulticastAddr (&NeighborCache->Neighbor, &Destination);
      Status = Ip6SelectSourceAddress (IpSb, &NeighborCache->Neighbor, &Source);
      if (EFI_ERROR (Status)) {
        return Status;
      }

      Status = Ip6SendNeighborSolicit (
                 IpSb,
                 &Source,
                 &Destination,
                 &NeighborCache->Neighbor,
                 &IpSb->SnpMode.CurrentAddress
                 );
      if (EFI_ERROR (Status)) {
        return Status;
      }

      --NeighborCache->Transmit;
      NeighborCache->Ticks = IP6_GET_TICKS (IpSb->RetransTimer) + 1;
    }

    NeighborCache->Interface = IpIf;
  }

  UpdatedExtHdrs       = NULL;
  ExtHdrsBackup        = NULL;
  NextHeaderBackup     = 0;
  FragmentHeadInserted = FALSE;

  //
  // Check whether we received Packet Too Big message for the packet sent to the
  // Destination. If yes include a Fragment Header in the subsequent packets.
  //
  RouteEntry = Ip6FindRouteEntry (
                 IpSb->RouteTable,
                 &Head->DestinationAddress,
                 NULL
                 );
  if (RouteEntry != NULL) {
    if ((RouteEntry->Flag & IP6_PACKET_TOO_BIG) == IP6_PACKET_TOO_BIG) {

      //
      // FragmentHead is inserted after Hop-by-Hop Options header, Destination
      // Options header (first occur), Routing header, and before Fragment header,
      // Authentication header, Encapsulating Security Payload header, and
      // Destination Options header (last occur), and upper-layer header.
      //
      Status = Ip6FillFragmentHeader (
                 IpSb,
                 Head->NextHeader,
                 LastHeaderBackup,
                 ExtHdrs,
                 ExtHdrsLen,
                 0,
                 &UpdatedExtHdrs
                 );
      if (EFI_ERROR (Status)) {
        return Status;
      }

      if ((ExtHdrs == NULL) && (ExtHdrsLen == 0)) {
        NextHeaderBackup = Head->NextHeader;
        Head->NextHeader = IP6_FRAGMENT;
      }

      ExtHdrsBackup    = ExtHdrs;
      ExtHdrs          = UpdatedExtHdrs;
      ExtHdrsLen       = ExtHdrsLen + sizeof (IP6_FRAGMENT_HEADER);
      RealExtLen       = RealExtLen + sizeof (IP6_FRAGMENT_HEADER);

      mIp6Id++;

      FragmentHeadInserted = TRUE;
    }

    Ip6FreeRouteEntry (RouteEntry);
  }

  //
  // OK, selected the source and route, fragment the packet then send
  // them. Tag each fragment other than the first one as spawn from it.
  // Each extension header is an integar multiple of 8 octets long, in
  // order to retain 8-octet alignment for subsequent headers.
  //
  Mtu     = IpSb->MaxPacketSize + sizeof (EFI_IP6_HEADER);
  HeadLen = sizeof (EFI_IP6_HEADER) + RealExtLen;

  if (Packet->TotalSize + HeadLen > Mtu) {
    //
    // Remove the inserted Fragment Header since we need fragment the packet.
    //
    if (FragmentHeadInserted) {
      ExtHdrs    = ExtHdrsBackup;
      ExtHdrsLen = ExtHdrsLen - sizeof (IP6_FRAGMENT_HEADER);

      if ((ExtHdrs == NULL) && (ExtHdrsLen == 0)) {
        Head->NextHeader = NextHeaderBackup;
      }
    }

    FragmentHdrsLen = ExtHdrsLen - UnFragmentHdrsLen;

    //
    // The packet is beyond the maximum which can be described through the
    // fragment offset field in Fragment header.
    //
    if ((((Packet->TotalSize + FragmentHdrsLen) >> 3) & (~0x1fff)) != 0) {
      Status = EFI_BAD_BUFFER_SIZE;
      goto Error;
    }

    if (FragmentHdrsLen != 0) {
      //
      // Append the fragmentable extension hdrs before the upper layer payload
      // to form a new NET_BUF. This NET_BUF contains all the buffer which will
      // be fragmented below.
      //
      TmpPacket = NetbufGetFragment (Packet, 0, Packet->TotalSize, FragmentHdrsLen);
      ASSERT (TmpPacket != NULL);

      //
      // Allocate the space to contain the fragmentable hdrs and copy the data.
      //
      Buf = NetbufAllocSpace (TmpPacket, FragmentHdrsLen, TRUE);
      ASSERT (Buf != NULL);
      CopyMem (Buf, ExtHdrs + UnFragmentHdrsLen, FragmentHdrsLen);

      //
      // Free the old Packet.
      //
      NetbufFree (Packet);
      Packet = TmpPacket;
    }

    //
    // The unfragment part which appears in every fragmented IPv6 packet includes
    // the IPv6 header, the unfragmentable extension hdrs and the fragment header.
    //
    UnFragmentLen = sizeof (EFI_IP6_HEADER) + UnFragmentHdrsLen + sizeof (IP6_FRAGMENT_HEADER);

    //
    // Mtu now is the length of the fragment part in a full-length fragment.
    //
    Mtu = (Mtu - UnFragmentLen) & (~0x07);
    Num = (Packet->TotalSize + Mtu - 1) / Mtu;

    for (Index = 0, Offset = 0, PacketLen = Mtu; Index < Num; Index++) {
      //
      // Get fragment from the Packet, append UnFragnmentLen spare buffer
      // before the fragmented data, the corresponding data is filled in later.
      //
      Fragment = NetbufGetFragment (Packet, Offset, PacketLen, UnFragmentLen);
      if (Fragment == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Error;
      }

      FragmentOffset = (UINT16) ((UINT16) Offset | 0x1);
      if (Index == Num - 1){
        //
        // The last fragment, clear the M flag.
        //
        FragmentOffset &= (~0x1);
      }

      Status = Ip6PrependHead (
                 IpSb,
                 Fragment,
                 Head,
                 FragmentOffset,
                 ExtHdrs,
                 ExtHdrsLen,
                 LastHeaderBackup,
                 UnFragmentLen
                 );
      ASSERT (Status == EFI_SUCCESS);

      Status = Ip6SendFrame (
                 IpIf,
                 IpInstance,
                 Fragment,
                 &NextHop,
                 Ip6SysPacketSent,
                 Packet
                 );
      if (EFI_ERROR (Status)) {
        goto Error;
      }

      //
      // The last fragment of upper layer packet, update the IP6 token status.
      //
      if ((Index == Num -1) && (Context != NULL)) {
        Wrap                = (IP6_TXTOKEN_WRAP *) Context;
        Wrap->Token->Status = Status;
      }

      Offset    += PacketLen;
      PacketLen = Packet->TotalSize - Offset;
      if (PacketLen > Mtu) {
        PacketLen = Mtu;
      }
    }

    NetbufFree (Packet);
    mIp6Id++;

    if (UpdatedExtHdrs != NULL) {
      FreePool (UpdatedExtHdrs);
    }

    return EFI_SUCCESS;
  }

  //
  // Need not fragment the packet, send it in one frame.
  //
  PacketHead = (EFI_IP6_HEADER *) NetbufAllocSpace (Packet, HeadLen, NET_BUF_HEAD);
  if (PacketHead == NULL) {
    Status = EFI_BAD_BUFFER_SIZE;
    goto Error;
  }

  CopyMem (PacketHead, Head, sizeof (EFI_IP6_HEADER));
  Packet->Ip.Ip6 = PacketHead;

  if (ExtHdrs != NULL) {
    Buf = (UINT8 *) (PacketHead + 1);
    CopyMem (Buf, ExtHdrs, ExtHdrsLen);
  }

  if (UpdatedExtHdrs != NULL) {
    //
    // A Fragment Header is inserted to the packet, update the payload length.
    //
    PacketHead->PayloadLength = (UINT16) (NTOHS (PacketHead->PayloadLength) +
                                sizeof (IP6_FRAGMENT_HEADER));
    PacketHead->PayloadLength = HTONS (PacketHead->PayloadLength);
    FreePool (UpdatedExtHdrs);
  }

  return Ip6SendFrame (
           IpIf,
           IpInstance,
           Packet,
           &NextHop,
           Callback,
           Context
           );

Error:
  if (UpdatedExtHdrs != NULL) {
    FreePool (UpdatedExtHdrs);
  }
  Ip6CancelPacket (IpIf, Packet, Status);
  return Status;
}

/**
  The filter function to find a packet and all its fragments.
  The packet's fragments have their Context set to the packet.

  @param[in]  Frame            The frames hold by the low level interface.
  @param[in]  Context          Context to the function, which is the packet.

  @retval TRUE                 This is the packet to cancel or its fragments.
  @retval FALSE                This is an unrelated packet.

**/
BOOLEAN
Ip6CancelPacketFragments (
  IN IP6_LINK_TX_TOKEN   *Frame,
  IN VOID                *Context
  )
{
  if ((Frame->Packet == (NET_BUF *) Context) || (Frame->Context == Context)) {
    return TRUE;
  }

  return FALSE;
}

/**
  Remove all the frames on the interface that pass the FrameToCancel,
  either queued on ARP queues or that have already been delivered to
  MNP and not yet recycled.

  @param[in]  Interface     Interface to remove the frames from.
  @param[in]  IoStatus      The transmit status returned to the frames' callback.
  @param[in]  FrameToCancel Function to select the frame to cancel; NULL to select all.
  @param[in]  Context       Opaque parameters passed to FrameToCancel. Ignored if
                            FrameToCancel is NULL.

**/
VOID
Ip6CancelFrames (
  IN IP6_INTERFACE          *Interface,
  IN EFI_STATUS             IoStatus,
  IN IP6_FRAME_TO_CANCEL    FrameToCancel   OPTIONAL,
  IN VOID                   *Context        OPTIONAL
  )
{
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *Next;
  IP6_LINK_TX_TOKEN         *Token;
  IP6_SERVICE               *IpSb;
  IP6_NEIGHBOR_ENTRY        *ArpQue;
  EFI_STATUS                Status;

  IpSb = Interface->Service;
  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);

  //
  // Cancel all the pending frames on ARP requests
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, &Interface->ArpQues) {
    ArpQue = NET_LIST_USER_STRUCT (Entry, IP6_NEIGHBOR_ENTRY, ArpList);

    Status = Ip6FreeNeighborEntry (
               IpSb,
               ArpQue,
               FALSE,
               FALSE,
               IoStatus,
               FrameToCancel,
               Context
               );
    ASSERT_EFI_ERROR (Status);
  }

  //
  // Cancel all the frames that have been delivered to MNP
  // but not yet recycled.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, &Interface->SentFrames) {
    Token = NET_LIST_USER_STRUCT (Entry, IP6_LINK_TX_TOKEN, Link);

    if ((FrameToCancel == NULL) || FrameToCancel (Token, Context)) {
      IpSb->Mnp->Cancel (IpSb->Mnp, &Token->MnpToken);
    }
  }
}

/**
  Cancel the Packet and all its fragments.

  @param[in]  IpIf                 The interface from which the Packet is sent.
  @param[in]  Packet               The Packet to cancel.
  @param[in]  IoStatus             The status returns to the sender.

**/
VOID
Ip6CancelPacket (
  IN IP6_INTERFACE    *IpIf,
  IN NET_BUF          *Packet,
  IN EFI_STATUS       IoStatus
  )
{
  Ip6CancelFrames (IpIf, IoStatus, Ip6CancelPacketFragments, Packet);
}

